package pers.kw.server.service;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import pers.kw.contants.RedissonContants;
import pers.kw.exceptions.BizException;
import pers.kw.server.persist.dao.StorageDao;
import pers.kw.server.persist.dao.StorageRecordDao;
import pers.kw.server.persist.entity.StorageDO;
import pers.kw.server.persist.entity.StorageRecordDO;
import pers.kw.storage.dto.UpdateStorageReqDto;
import pers.kw.utils.DoWrapUtil;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * 库存服务
 */
@Slf4j
@Service
public class StorageService {

    @Resource
    private StorageDao storageDao;

    @Resource
    private StorageRecordDao storageRecordDao;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private StorageService storageService;

    public void testTx(UpdateStorageReqDto reqDto) {
        //方法内部调用，事务会失效
        //this.updateStorage(reqDto);

        //解决1：自己注入自己
        //storageService.updateStorage(reqDto);

        //解决2：使用代理
        StorageService storageService = (StorageService) AopContext.currentProxy();
        storageService.updateStorage(reqDto);
    }

    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void updateStorage(UpdateStorageReqDto reqDto) {
        log.info("更新库存start...入参：{}", JSON.toJSONString(reqDto));
        StorageDO storageDO = storageDao.findByGoodsNo(reqDto.getGoodsNo());
        if (storageDO == null) {
            throw new BizException("商品库存信息不存在");
        }
        //更新库存
        if (storageDO.getGoodsNum() < reqDto.getGoodsNum()) {
            throw new BizException("库存不足");
        }
        Long afterNum = storageDO.getGoodsNum() - reqDto.getGoodsNum();
        Long beforeNum = storageDO.getGoodsNum();
        storageDO.setGoodsNum(afterNum);
        DoWrapUtil.update(storageDO, false);
        storageDao.save(storageDO);
        //保存库存变更记录
        StorageRecordDO storageRecordDO = new StorageRecordDO();
        DoWrapUtil.insert(storageRecordDO, false, false);
        storageRecordDO.setGoodsNo(storageDO.getGoodsNo());
        storageRecordDO.setBeforeNum(beforeNum);
        storageRecordDO.setAfterNum(afterNum);
        storageRecordDO.setMemo(reqDto.getMemo());
        storageRecordDao.save(storageRecordDO);

        if (reqDto.getError() != null && reqDto.getError() == 1) {
            throw new BizException("测试异常回滚");
        }
        log.info("更新库存end...");
    }

    public void batchUpdateStorage(UpdateStorageReqDto reqDto) throws InterruptedException {
        RLock rLock = redissonClient.getLock(RedissonContants.USERNAME_LOCK_KEY);
        //1. lock() 该方法无返回值，会一直阻塞，直到获取到锁。ps：若超过了ribbon设置的调用时长会怎么样
        //rLock.lock();
        //有重载方法，可以指定多少的过期时间和续期时间
        rLock.lock(10L, TimeUnit.SECONDS);

        //可以考虑使用tryLock
        //2. 无参数 获取不到锁，直接失败
//        if (!rLock.tryLock()) {
//            throw new BizException("稍等。。。");
//        }

        //3. 等3s，获取不到，直接失败
//        if (!rLock.tryLock(3L, TimeUnit.SECONDS)) {
//            throw new BizException("稍等。。。");
//        }

        //4. 等3s，获取不到，直接失败；leaseTime 表示自动解锁时间，-1 代表不自动解锁
        //5s后，自动解锁；finally中的自动解锁代码不会执行
//        if (!rLock.tryLock(3L, 5, TimeUnit.SECONDS)) {
//            throw new BizException("稍等。。。");
//        }


        //最好指定过期时间
        //lock有很多重载方法，具体看文档
        log.info("加锁成功。。。");
        try {
            StorageService storageService = (StorageService) AopContext.currentProxy();
            try {
                //此处代码用于测试是否会续期
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            storageService.updateStorage(reqDto);
        } finally {
            try {
                if (rLock.isLocked() && rLock.isHeldByCurrentThread()) {
                    rLock.unlock();
                    log.info("自动释放锁成功。。。");
                }
            } catch (Exception e) {
                log.error("自动释放锁失败", e);
            }
        }
    }
}
